﻿/*
 * Created by SharpDevelop.
 * User: issuser
 * Date: 2010-12-6
 * Time: 17:35
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Linq;
using NLite.Data;

using System.Data;
using NLite.Data.SqlTypes;
using NLite.Data.Dialect.Function;

namespace NLite.Data.Dialect
{
	public class MsSql2000Dialect : Dialect
    {
        public override string IdentitySelectString
        {
            get
            {
                return "select CAST( SCOPE_IDENTITY() AS BIGINT)";
            }
        }

        public MsSql2000Dialect()
        {
        }

        protected override void RegisterColumnTypes()
        {
            base.RegisterColumnTypes();
            RegisterColumnType(DbTypeCode.AnsiStringFixedLength, "CHAR(255)");
            RegisterColumnType(DbTypeCode.AnsiStringFixedLength, 8000, "CHAR($l)");
            RegisterColumnType(DbTypeCode.AnsiString, "VARCHAR(255)");
            RegisterColumnType(DbTypeCode.AnsiString, 8000, "VARCHAR($l)");
            RegisterColumnType(DbTypeCode.AnsiString, 2147483647, "TEXT");
            RegisterColumnType(DbTypeCode.Clob, 2147483647, "TEXT");
            RegisterColumnType(DbTypeCode.Binary, "VARBINARY(8000)");
            RegisterColumnType(DbTypeCode.Binary, 8000, "VARBINARY($l)");
            RegisterColumnType(DbTypeCode.Binary, 2147483647, "IMAGE");
            RegisterColumnType(DbTypeCode.Blob, 2147483647, "IMAGE");
            RegisterColumnType(DbTypeCode.Boolean, "BIT");
            RegisterColumnType(DbTypeCode.Byte, "TINYINT");
            RegisterColumnType(DbTypeCode.Currency, "MONEY");
            RegisterColumnType(DbTypeCode.Date, "DATETIME");
            RegisterColumnType(DbTypeCode.DateTime, "DATETIME");
            RegisterColumnType(DbTypeCode.Decimal, "DECIMAL(19,5)");
            RegisterColumnType(DbTypeCode.Decimal, 19, "DECIMAL($pair, $s)");
            RegisterColumnType(DbTypeCode.Double, "DOUBLE PRECISION"); //synonym for FLOAT(53)
            RegisterColumnType(DbTypeCode.Guid, "UNIQUEIDENTIFIER");
            RegisterColumnType(DbTypeCode.Int16, "SMALLINT");
            RegisterColumnType(DbTypeCode.Int32, "INT");
            RegisterColumnType(DbTypeCode.Int64, "BIGINT");
            RegisterColumnType(DbTypeCode.Single, "REAL"); //synonym for FLOAT(24) 
            RegisterColumnType(DbTypeCode.StringFixedLength, "NCHAR(255)");
            RegisterColumnType(DbTypeCode.StringFixedLength, 4000, "NCHAR($l)");
            RegisterColumnType(DbTypeCode.String, "NVARCHAR(255)");
            RegisterColumnType(DbTypeCode.String, 4000, "NVARCHAR($l)");
            RegisterColumnType(DbTypeCode.String, 1073741823, "NTEXT");
            RegisterColumnType(DbTypeCode.NClob, 1073741823, "NTEXT");
            RegisterColumnType(DbTypeCode.Time, "DATETIME");
        }

        protected override void RegisterStringFunction()
        {
            RegisterFunction(FunctionType.String.SubString, new FunctionTemplateView("substring(?1, ?2, ?3)"));
            RegisterFunction(FunctionType.String.Left, new FunctionTemplateView("left(?1, ?2)"));
            RegisterFunction(FunctionType.String.Right, new FunctionTemplateView("right(?1, ?2)"));
            RegisterFunction(FunctionType.String.IndexOf, new CharIndexView());
            RegisterFunction(FunctionType.String.Concat, new VarArgsFunctionView("(", "+", ")"));
            RegisterFunction(FunctionType.String.Upper, new StandardFunctionView("upper"));
            RegisterFunction(FunctionType.String.Lower, new StandardFunctionView("lower"));
            RegisterFunction(FunctionType.String.Length, new StandardFunctionView("len"));
            RegisterFunction(FunctionType.String.LTrim, new StandardFunctionView("ltrim"));
            RegisterFunction(FunctionType.String.RTrim, new StandardFunctionView("rtrim"));
            RegisterFunction(FunctionType.String.Trim, new TrimFunctionView());
            RegisterFunction(FunctionType.String.Replace, new StandardSafeFunctionView("replace", 3));
            RegisterFunction(FunctionType.String.Remove, new RemoveFunctionView());


            RegisterFunction(FunctionType.ToChar, new StandardFunctionView("char"));
            RegisterFunction(FunctionType.ToNChar, new StandardFunctionView("nchar"));

            RegisterFunction(FunctionType.Case,new CaseFunctionView());
            RegisterFunction(FunctionType.Cast, new CastFunctionView());
            
        }

        class TrimFunctionView : IFunctionView
        {
            public virtual void Render(NodeWriter visitor, params INode[] args)
            {
                if (args.Length != 1)
                {
                    throw new ArgumentException("function ' left takes 1 arguments.");
                }

                var targetArg = args[0];

                visitor.Builder.Append("RTRIM(LTRIM(");
                visitor.Visit(targetArg);
                visitor.Builder.Append("))");
               
            }
        }
        class RemoveFunctionView : IFunctionView
        {
            public virtual void Render(NodeWriter visitor, params INode[] args)
            {
                if (args.Length != 2 && args.Length != 3)
                {
                    throw new ArgumentException("function ' left takes 2 or 3 arguments.");
                }

                visitor.Builder.Append("STUFF(");
                visitor.Visit(args[0]);
                visitor.Builder.Append(", ");
                visitor.Visit(args[1]);
                visitor.Builder.Append(", ");
                if (args.Length == 3)
                {
                    visitor.Visit(args[2]);
                }
                else
                {
                    visitor.Builder.Append("8000");
                }
                visitor.Builder.Append(", '')");

            }
        }
        class CharIndexView : IFunctionView
        {
            public virtual void Render(NodeWriter visitor, params INode[] args)
            {
                if (args.Length != 2 && args.Length != 3)
                {
                    throw new ArgumentException("function ' left takes 2 or 3 arguments.");
                }
                visitor.Builder.Append("CHARINDEX(");
                visitor.Visit(args[1]);
                visitor.Builder.Append(", ");
                visitor.Visit(args[0]);
                if (args.Length == 3)
                {
                    visitor.Builder.Append(", ");
                    visitor.Visit(args[2]);
                }
                visitor.Builder.Append(")");
            }
        }

        protected override void RegisterMathFunction()
        {
            //RegisterFunction("absval", new StandardFunctionView("absval"));
            //RegisterFunction("cot", new StandardFunctionView("cot"));
            //RegisterFunction("degrees", new StandardFunctionView("degrees"));
            //RegisterFunction("float", new StandardFunctionView("float"));
            //RegisterFunction("hex", new StandardFunctionView("hex"));
            //RegisterFunction("mod", new FunctionTemplateView("((?1) % (?2))"));
            //RegisterFunction("radians", new StandardFunctionView("radians"));
            //RegisterFunction("soundex", new StandardFunctionView("soundex"));
            //RegisterFunction("stddev", new StandardFunctionView("stddev"));

            RegisterFunction(FunctionType.Math.Random, new NoArgFunctionView("rand"));

            RegisterFunction(FunctionType.Math.Abs, new StandardFunctionView("abs"));
            RegisterFunction(FunctionType.Math.Sign, new StandardFunctionView("sign"));
            RegisterFunction(FunctionType.Math.Ceiling, new StandardFunctionView("ceiling"));
            RegisterFunction(FunctionType.Math.Floor, new StandardFunctionView("floor"));
            RegisterFunction(FunctionType.Math.Round, new StandardFunctionView("round"));
            RegisterFunction(FunctionType.Math.Acos, new StandardFunctionView("acos"));
            RegisterFunction(FunctionType.Math.Asin, new StandardFunctionView("asin"));
            RegisterFunction(FunctionType.Math.Atan, new StandardFunctionView("atan"));
            RegisterFunction(FunctionType.Math.Atan2, new StandardFunctionView("atn2"));
            RegisterFunction(FunctionType.Math.Cos, new StandardFunctionView("cos"));
            RegisterFunction(FunctionType.Math.Exp, new StandardFunctionView("exp"));
            RegisterFunction(FunctionType.Math.Log, new StandardFunctionView("log"));
            RegisterFunction(FunctionType.Math.Log10, new StandardFunctionView("log10"));
            RegisterFunction(FunctionType.Math.Sin, new StandardFunctionView("sin"));
            RegisterFunction(FunctionType.Math.Sqrt, new StandardFunctionView("sqrt"));
            RegisterFunction(FunctionType.Math.Tan, new StandardFunctionView("tan"));

            RegisterFunction(FunctionType.Math.Mod, new VarArgsFunctionView("", " % ", ""));
        }

        protected override void RegisterDateFunction()
        {
            RegisterFunction(FunctionType.DateTime.Current, new NoArgFunctionView("current_date"));
            RegisterFunction(FunctionType.DateTime.New, new FunctionTemplateView("CAST(?1 as DateTime)"));

            RegisterFunction(FunctionType.DateTime.Subtract, new StandardFunctionView("DATEDIFF"));

            RegisterFunction(FunctionType.DateTime.GetSecond, new FunctionTemplateView("datepart(second, ?1)"));
            RegisterFunction(FunctionType.DateTime.GetMinute, new FunctionTemplateView("datepart(minute, ?1)"));
            RegisterFunction(FunctionType.DateTime.GetHour, new FunctionTemplateView("datepart(hour, ?1)"));
            RegisterFunction(FunctionType.DateTime.GetDay, new FunctionTemplateView("datepart(day, ?1)"));
            RegisterFunction(FunctionType.DateTime.GetMonth, new FunctionTemplateView("datepart(month, ?1)"));
            RegisterFunction(FunctionType.DateTime.GetYear, new FunctionTemplateView("datepart(month, ?1)"));

            RegisterFunction(FunctionType.DateTime.GetMillisecond, new FunctionTemplateView("datepart(millisecond, ?1)"));
            RegisterFunction(FunctionType.DateTime.GetDayOfWeek, new FunctionTemplateView("(datepart(weekday, ?1)-1)"));
            RegisterFunction(FunctionType.DateTime.GetDayOfYear, new FunctionTemplateView("(datepart(dayofyear, ?1)-1)"));

            RegisterFunction(FunctionType.DateTime.AddSecond, new FunctionTemplateView("dateadd(second, ?2,?1)"));
            RegisterFunction(FunctionType.DateTime.AddMinute, new FunctionTemplateView("dateadd(minute, ?2,?1)"));
            RegisterFunction(FunctionType.DateTime.AddHour, new FunctionTemplateView("dateadd(hour, ?2,?1)"));
            RegisterFunction(FunctionType.DateTime.AddDay, new FunctionTemplateView("dateadd(day, ?2,?1)"));
            RegisterFunction(FunctionType.DateTime.AddMonth, new FunctionTemplateView("dateadd(month, ?2,?1)"));
            RegisterFunction(FunctionType.DateTime.AddYear, new FunctionTemplateView("dateadd(month, ?2,?1)"));
        }

        public override bool SupportsCollectionOperation(CollectionOperator type)
        {
            return type == CollectionOperator.Intersect || type == CollectionOperator.Union;
        }

        public override void BuildSelect(NodeWriter visitor, SelectNode select)
        {
            if (select == null)
                throw new ArgumentNullException("select");

            if (select.PageIndex <= 0)
                BuildBasicSelect(visitor, select);
            else
                BuildSelectWithOffset(visitor,select);

            //TODO:for update
        }

        protected virtual void BuildSelectWithOffset(NodeWriter visitor, SelectNode select)
        {
            throw new NotSupportedException("");
        }

        protected void BuildBasicSelect(NodeWriter visitor, SelectNode select)
        {
            visitor.Builder.Append("SELECT ");

            if (select.IsDistinct)
                visitor.Builder.Append("DISTINCT ");

            if (select.PageSize > 0)
            {
                visitor.Builder.Append("TOP ");
                visitor.Builder.Append(select.PageSize.ToString());
                visitor.Builder.Append(" ");
            }

            if (select.Projections.Count > 0)
                visitor.VisitEnumerable(select.Projections.ToArray());
            else
                visitor.Builder.Append("*");

            if (select.Into != null)
                visitor.Visit(select.Into);

            visitor.Builder.AppendLine().Append("FROM ");
            var table = new TableNode(select.Name, select.Alias);
            table.Accept(visitor);

            if (select.Joins.Count > 0)
            {
                visitor.Builder.AppendLine();
                visitor.VisitEnumerable(select.Joins.ToArray(), " ");
            }

            select.Where.Accept(visitor);
            select.Group.Accept(visitor);
            select.Having.Accept(visitor);
            select.OrderBy.Accept(visitor);
        }
    }

}
